/*
Copyright 2014 Yury Kharlamov

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

Generated Inversion-Of-Control container
https://code.google.com/p/gen-ioc/

* * *

Unit tests for Abstract State Machine implementation
*/
#include "stdafx.h"
#include <StateMachine/state_machine.h>

using namespace state_machine;

class ASMTest : public ::testing::Test
{
protected:

	struct Finished
	{ 
		Finished(bool success = false) :success(success){}
		bool success;
	};
	struct Started{};
	struct
	{
		Event<Finished> Finished;
		Event<Started> Started;
	} E;


	ASMTest() :sm(s1.GetValue()), s1("S1"), s2("S2"), s3("S3"), s4("S4"), s5("S5"), _(State<std::string>::Any()), isActionCalled(false){}
	typedef State<std::string> StrState;
	StrState s1, s2, s3, s4, s5, _;
	ASM<std::string> sm;

	void DoAction()
	{
		isActionCalled = true;
	}

	bool isActionCalled;
};

#if _MSC_VER<1800
	#define ref
#else
	#define ref &
#endif

TEST_F(ASMTest, TrueEventRule)
{
	bool success = false;

	sm.AddRule(s1 + Finished() > s2 - [&](const Finished ref e){success = e.success;});

	ASSERT_EQ(s1.GetValue(), sm.GetState());
	sm.QueueEvent(Finished(true));
	ASSERT_EQ(s2.GetValue(), sm.GetState());
	ASSERT_EQ(true, success);
}

TEST_F(ASMTest, FalseEventRule)
{
	sm.AddRule(s1 + Started() > s2);

	ASSERT_EQ(s1.GetValue(), sm.GetState());
	sm.QueueEvent(Finished());
	ASSERT_EQ(s1.GetValue(), sm.GetState());
}

TEST_F(ASMTest, TrueAutoRuleOnAnyEvent)
{
	sm.AddRule(s1 / []{return true; } > s2);

	ASSERT_EQ(s1.GetValue(), sm.GetState());
	sm.QueueEvent(123);
	ASSERT_EQ(s2.GetValue(), sm.GetState());
}

TEST_F(ASMTest, FalseAutoRuleOnAnyEvent)
{
	sm.AddRule(s1 / []{return false; } > s2);

	ASSERT_EQ(s1.GetValue(), sm.GetState());
	sm.QueueEvent(123);
	ASSERT_EQ(s1.GetValue(), sm.GetState());
}

TEST_F(ASMTest, TrueEventGuardRule)
{
	sm.AddRule(s1 / [](const Finished ref e){ return e.success; } > s2);

	ASSERT_EQ(s1.GetValue(), sm.GetState());
	sm.QueueEvent(Finished(true));
	ASSERT_EQ(s2.GetValue(), sm.GetState());
}

TEST_F(ASMTest, FalseEventGuardRule)
{
	sm.AddRule(s1 / [](const Finished ref e){ return e.success; } > s2 - [=]{ DoAction(); });

	ASSERT_EQ(s1.GetValue(), sm.GetState());
	sm.QueueEvent(Finished(false));
	ASSERT_EQ(s1.GetValue(), sm.GetState());
	ASSERT_FALSE(isActionCalled);
}

TEST_F(ASMTest, FromAnyStateRule)
{
	sm.AddRule(_ + Finished() > s2);

	ASSERT_EQ(s1.GetValue(), sm.GetState());
	sm.QueueEvent(Finished());
	ASSERT_EQ(s2.GetValue(), sm.GetState());
}

TEST_F(ASMTest, FromSeveralStatesRule)
{
	sm.AddRule(s2 | s1 + Finished() > s3 - [=]{ DoAction(); });

	ASSERT_EQ(s1.GetValue(), sm.GetState());
	sm.QueueEvent(Finished());
	ASSERT_EQ(s3.GetValue(), sm.GetState());
	ASSERT_TRUE(isActionCalled);
}


TEST_F(ASMTest, OnBeforeStateAction)
{
	int value = 1;
	sm += 
		s1 + Finished() > s2 - [&]{ASSERT_EQ(3, value); value = 2; }, 
		s2.OnBefore([&]{ ASSERT_EQ(1, value); value = 3; });

	sm.QueueEvent(Finished());
	ASSERT_EQ(s2.GetValue(), sm.GetState());
	ASSERT_EQ(2, value);
}

TEST_F(ASMTest, OnAfterStateAction)
{
	int value = 1;
	sm +=
		s1 + Finished() > s2 - [&]{ASSERT_EQ(1, value); value = 2; },
		s2.OnAfter([&]{ ASSERT_EQ(2, value); value = 3; });

	sm.QueueEvent(Finished());
	ASSERT_EQ(s2.GetValue(), sm.GetState());
	ASSERT_EQ(3, value);
}

TEST_F(ASMTest, OnLeaveStateAction)
{
	int value = 1;
	sm +=
		s1 + Finished() > s2 - [&]{ASSERT_EQ(3, value); value = 2; },
		s1.OnLeave([&]{ASSERT_EQ(1, value); value = 3; });

	sm.QueueEvent(Finished());
	ASSERT_EQ(s2.GetValue(), sm.GetState());
	ASSERT_EQ(2, value);
}

TEST_F(ASMTest, OnAfterTransitionAction)
{
	int value = 1;
	sm +=
		s1 + Finished() > s2 - [&]{ASSERT_EQ(1, value); value = 2; };
	sm.SetOnAfterTransition(
		[&value, this](const std::string& from, const std::string& to)
	{
		ASSERT_EQ(s1.GetValue(), from);
		ASSERT_EQ(s2.GetValue(), to);
		ASSERT_EQ(2, value);
		value = 3;
	});
	sm.QueueEvent(Finished());
	ASSERT_EQ(3, value);
}

TEST_F(ASMTest, OnBeforeTransitionAction)
{
	int value = 1;
	sm +=
		s1 + Finished() > s2 - [&]{ASSERT_EQ(3, value); value = 2; };
	sm.SetOnBeforeTransition(
		[&value, this](const std::string& from, const std::string& to)
	{
		ASSERT_EQ(s1.GetValue(), from);
		ASSERT_EQ(s2.GetValue(), to);
		ASSERT_EQ(1, value);
		value = 3;
	});
	sm.QueueEvent(Finished());
	ASSERT_EQ(2, value);
}

TEST_F(ASMTest, HandlerOrderTest)
{
	int value = 1;
	sm +=
		s1 + Finished() > s2 - [&]{ASSERT_EQ(6, value); value = 2; },
		s2 + Finished() > s3 - [&]{ASSERT_EQ(9, value); value = 10; },
		s1.OnBefore([&]{value = 3; }),
		s1.OnAfter([&]{value = 4; }),
		s1.OnLeave([&]{ASSERT_EQ(1, value); value = 5; }),
		s2.OnBefore([&]{ASSERT_EQ(5, value); value = 6; }),
		s2.OnAfter([&]{ASSERT_EQ(2, value); value = 7; }),
		s2.OnLeave([&]{ASSERT_EQ(7, value); value = 8; }),
		s3.OnBefore([&]{ASSERT_EQ(8, value); value = 9; }),
		s3.OnAfter([&]{ASSERT_EQ(10, value); value = 11; }),
		s3.OnLeave([&]{value = 12; }),

	sm.SetOnBeforeTransition(
		[&value, this](const std::string& from, const std::string& to)
	{
		ASSERT_EQ(s1.GetValue(), from);
		ASSERT_EQ(s2.GetValue(), to);
		ASSERT_EQ(1, value);
	});
	sm.SetOnAfterTransition(
		[&value, this](const std::string& from, const std::string& to)
	{
		ASSERT_EQ(s1.GetValue(), from);
		ASSERT_EQ(s2.GetValue(), to);
		ASSERT_EQ(7, value);
	});
	sm.QueueEvent(Finished());

	sm.SetOnBeforeTransition(
		[&value, this](const std::string& from, const std::string& to)
	{
		ASSERT_EQ(s2.GetValue(), from);
		ASSERT_EQ(s3.GetValue(), to);
		ASSERT_EQ(7, value);
	});
	sm.SetOnAfterTransition(
		[&value, this](const std::string& from, const std::string& to)
	{
		ASSERT_EQ(s2.GetValue(), from);
		ASSERT_EQ(s3.GetValue(), to);
		ASSERT_EQ(11, value);
	});
	sm.QueueEvent(Finished());
	ASSERT_EQ(11, value);
}

TEST_F(ASMTest, AutoRule)
{
	sm +=
		s1 + Finished() > s2,
		s2 > s3;
	sm.QueueEvent(Finished());
	ASSERT_EQ(s3.GetValue(), sm.GetState());
}

TEST_F(ASMTest, ErrorTestWithErrorState)
{
	sm +=
		s1 + Finished() > s2 - [&]{ throw std::logic_error("some error"); },
		s2 > s3,
		s2 + Error() > s4;
	sm.QueueEvent(Finished());
	ASSERT_EQ(s4.GetValue(), sm.GetState());
}

TEST_F(ASMTest, RuleOrderImportant)
{
	sm +=
		s1 + Finished() > s2,
		s2 > s3,
		s2 > s4;
	sm.QueueEvent(Finished());
	ASSERT_EQ(s3.GetValue(), sm.GetState());
}

TEST_F(ASMTest, RuleOrderImportant2)
{
	sm +=
		s1 + Finished() > s2,
		s2 > s4,
		s2 > s3;
	sm.QueueEvent(Finished());
	ASSERT_EQ(s4.GetValue(), sm.GetState());
}

TEST_F(ASMTest, InvertedRule)
{
	sm +=
		s1 + Finished() > s2,
		~s2 > s3;
	sm.QueueEvent(Finished());
	ASSERT_EQ(s2.GetValue(), sm.GetState());
}

TEST_F(ASMTest, SeveralInvertedStates)
{
	sm +=
		s1 + Finished() > s2,
		~s1|s3|s4 > s3;
	sm.QueueEvent(Finished());
	ASSERT_EQ(s3.GetValue(), sm.GetState());
}

TEST_F(ASMTest, SeveralStates)
{
	sm +=
		s1 + Finished() > s2,
		s3 | s2 | s4 > s5;
	sm.QueueEvent(Finished());
	ASSERT_EQ(s5.GetValue(), sm.GetState());
}

TEST_F(ASMTest, SeveralStatesWithGuard)
{
	sm +=
		s3 | s1 | s4 / [](const Finished& e){return true; } > s5;
	sm.QueueEvent(Finished());
	ASSERT_EQ(s5.GetValue(), sm.GetState());
}

TEST_F(ASMTest, SeveralStatesWithEvent)
{
	sm +=
		s3 | s4 | s1 + Finished() > s5;
	sm.QueueEvent(Finished());
	ASSERT_EQ(s5.GetValue(), sm.GetState());
}

TEST_F(ASMTest, SeveralInvertedStatesWithGuard)
{
	sm +=
		~ s3 | s2 | s4 / [](const Finished& e){return true; } > s5;
	sm.QueueEvent(Finished());
	ASSERT_EQ(s5.GetValue(), sm.GetState());
}

TEST_F(ASMTest, SeveralInvertedStatesWithEvent)
{
	sm +=
		~ s3 | s4 | s2 + Finished() > s5;
	sm.QueueEvent(Finished());
	ASSERT_EQ(s5.GetValue(), sm.GetState());
}


TEST_F(ASMTest, SeveralStatesWithGuardNoMatch)
{
	sm +=
		s3 | s2 | s4 / [](const Finished& e){return true; } > s5;
	sm.QueueEvent(Finished());
	ASSERT_EQ(s1.GetValue(), sm.GetState());
}

TEST_F(ASMTest, SeveralStatesWithEventNoMatch)
{
	sm +=
		s3 | s4 | s2 + Finished() > s5;
	sm.QueueEvent(Finished());
	ASSERT_EQ(s1.GetValue(), sm.GetState());
}

TEST_F(ASMTest, SeveralInvertedStatesWithGuardNoMatch)
{
	sm +=
		~s1 | s2 | s4 / [](const Finished& e){return true; } > s5;
	sm.QueueEvent(Finished());
	ASSERT_EQ(s1.GetValue(), sm.GetState());
}

TEST_F(ASMTest, SeveralInvertedStatesWithEventNoMatch)
{
	sm +=
		~s3 | s4 | s1 + Finished() > s5;
	sm.QueueEvent(Finished());
	ASSERT_EQ(s1.GetValue(), sm.GetState());
}

TEST_F(ASMTest, ActionOnEvent)
{
	int value = 0;
	sm +=
		_ + Finished() > [&]{ value = 1; };
	sm.QueueEvent(Finished());
	ASSERT_EQ(1, value);
}

TEST_F(ASMTest, ActionWithArgOnEvent)
{
	bool result = false;
	sm +=
		_ + Finished() > [&](const Finished ref e){ result = e.success; };
	sm.QueueEvent(Finished(true));
	ASSERT_EQ(true, result);
}

GTEST_API_ int main(int argc, char **argv) {
	printf("Running main() from gtest_main.cc\n");
	testing::InitGoogleTest(&argc, argv);
	auto res = RUN_ALL_TESTS();
	_getch();
	return res;
}

